package com.example.Holo.rework.dao.implementation.sqlite.android;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.Holo.rework.dao.interfaces.IDaoFactory;
import com.example.Holo.rework.dao.interfaces.IGenericDao;
import com.google.common.base.Function;
import com.google.common.base.Supplier;

import java.util.ArrayList;
import java.util.List;

import static com.example.Holo.rework.dao.implementation.sqlite.android.Column.ID_COLUMN;
import static com.google.common.base.Joiner.on;
import static com.google.common.collect.Lists.newArrayList;

abstract class GenericDao<T> implements IGenericDao<T> {

    private final Context context;
    private final String databaseName;
    private final IDaoFactory daoFactory;

    protected GenericDao(Context context, String databaseName, SQLiteDaoFactory daoFactory) {
        this.databaseName = databaseName;
        this.context = context;
        this.daoFactory = daoFactory;
    }

    protected abstract String getTableName();

    protected abstract List<ColumnDescription> getColumns();

    protected abstract ContentValues getContentValues(T object);

    protected abstract T createFromCursor(Cursor cursor);

    protected abstract Column getIdColumnName();

    protected SQLiteDatabase getWritableDatabase() {
        return new DbHelper(context, databaseName, daoFactory).getWritableDatabase();
    }

    @Override
    public final void createStorage(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE " + getTableName() + " (" + on(", ").join(joinAllColumnsAndTypes()) + ")");
    }

    protected final Function<Cursor, List<T>> cursorToArrayFunction = new Function<Cursor, List<T>>() {
        @Override
        public List<T> apply(Cursor cursor) {
            final List<T> objects = new ArrayList<T>();
            while (cursor.moveToNext()) {
                objects.add(createFromCursor(cursor));
            }
            return objects;
        }
    };

    private final Supplier<Cursor> cursorFactory = new Supplier<Cursor>() {
        @Override
        public Cursor get() {
            final SQLiteDatabase database = getWritableDatabase();
            return database.query(getTableName(), getAllColumns(), null, null, null, null, null);
        }
    };

    @Override
    public final List<T> getAll() {
        return withinCursor(cursorFactory, cursorToArrayFunction);
    }

    @Override
    public long add(T object) {
        final SQLiteDatabase database = getWritableDatabase();
        final long generatedId = database.insert(getTableName(), null, getContentValues(object));
        final ContentValues values = new ContentValues();
        values.put(getIdColumnName().name, generatedId);
        database.update(getTableName(), values, getIdColumnName() + " = " + -1, null);
        return generatedId;
    }

    @Override
    public int remove(long id) {
        final SQLiteDatabase database = getWritableDatabase();
        return database.delete(getTableName(), getIdColumnName() + " = " + id, null);
    }

    @Override
    public T getEntityById(final long entityId) {
        final SQLiteDatabase database = getWritableDatabase();
        final Supplier<Cursor> cursorFactory = new Supplier<Cursor>() {
            @Override
            public Cursor get() {
                return database.query(getTableName(), getAllColumns(), getIdColumnName() + " = " + entityId,
                        null, null, null, null);
            }
        };
        final Function<Cursor, T> cursorToResult = new Function<Cursor, T>() {
            @Override
            public T apply(Cursor cursor) {
                cursor.moveToFirst();
                return createFromCursor(cursor);
            }
        };
        return withinCursor(cursorFactory, cursorToResult);
    }

    private Iterable<String> joinAllColumnsAndTypes() {
        final List<String> definitions = newArrayList();
        final List<ColumnDescription> columns = getColumns();
        for ( ColumnDescription columnDescription : columns ) {
            final Column column = columnDescription.column;
            final StringBuilder definition = new StringBuilder();
            definition.append(column.name).append(" ").append(column.type);
            final PrimaryKey primaryKey = columnDescription.primaryKey;
            if ( primaryKey != null ) {
                definition.append(" ").append("PRIMARY KEY");
                if (primaryKey.isAutoincrement) {
                    definition.append(" ").append("AUTOINCREMENT");
                }
            }
            definitions.add(definition.toString());
        }
        return definitions;
    }

    protected static <T> T withinCursor(Supplier<Cursor> cursorFactory, Function<Cursor, T> cursorToResult) {
        final Cursor cursor = cursorFactory.get();
        try {
            return cursorToResult.apply(cursor);
        } finally {
            cursor.close();
        }
    }

    private static final Function<?, ?> self = new Function<Object, Object>() {
        @Override
        public Object apply(Object object) {
            return object;
        }
    };

    public String[] getAllColumns() {
        final List<String> columns = new ArrayList<String>();
        for (ColumnDescription columnDescription : getColumns()) {
            columns.add(columnDescription.column.name);
        }
        return columns.toArray(new String[columns.size()]);
    }
}
